﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data.SqlTypes;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using BIStudio.Framework.Utils;
using BIStudio.Framework;

namespace BIStudio.Framework.Log
{
    [Description("Log4")]
    public class Log4LogProvider : ILog
    {
        private log4net.ILog log = log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
        /// <summary>
        /// 分割行的标识串
        /// </summary>
        private const string LineSplitString = "\r\n------------\r\n";

        /// <summary>
        /// 分割字段的标识串
        /// </summary>
        private const string FieldSplitString = ";\r\n";

        /// <summary>
        /// 分割字段名和字段值的标识串
        /// </summary>
        private const string ValueSplitString = "::";

        private static readonly object LockHelper = new object();

        #region ILog

        public void WriteLog(STDLog info)
        {
            string contents = string.Format("{0} {1}", FormatInfo(info), LineSplitString);
            switch ((LogLevelEnum)info.LogLevel)
            {
                case LogLevelEnum.OFF:
                    break;
                case LogLevelEnum.ERROR:
                    log.Error(contents);
                    break;
                case LogLevelEnum.WARN:
                    log.Warn(contents);
                    break;
                case LogLevelEnum.INFO:
                    log.Info(contents);
                    break;
                case LogLevelEnum.DEBUG:
                    log.Debug(contents);
                    break;
                case LogLevelEnum.FATAL:
                    log.Fatal(contents);
                    break;
            }
        }

        public List<STDLog> ReadLogs(LogCondtiton condtion)
        {
            if (condtion.LogLevel == null)
                return null;
            string path = Path.GetDirectoryName(GetLogFile(condtion.EntityModule, (TraceLevel)condtion.LogLevel.Value));
            lock (LockHelper)
            {
                try
                {
                    if (path != null && !Directory.Exists(path))
                        return null;
                    return Directory.GetFiles(path).SelectMany(data => File.ReadAllText(data).Split(LineSplitString)
                                .AsParallel()
                                .Where(item => !string.IsNullOrEmpty(item))
                                .Select(DeFormatInfo)
                                .Where(item => condtion.GetCondition()(item))).ToList();
                }
                catch (Exception ex)
                {
                    return null;
                }
            }
        }

        /// <summary>
        /// 获取log文件的路径
        /// </summary>
        /// <param name="entityModule"></param>
        /// <param name="level"></param>
        /// <returns></returns>
        private string GetLogFile(string entityModule, TraceLevel level)
        {
            string path =
                ALConfig.DllConfigs.IsExists(entityModule) &&
                ALConfig.DllConfigs[entityModule].IsExists("Log") &&
                ALConfig.DllConfigs[entityModule]["Log"].IsExists("Path") ?
                ALConfig.DllConfigs[entityModule]["Log"]["Path"] :
                "Log\\" + entityModule;
            return Path.Combine(CFContext.Server.GetBaseDirectory() + "/" + path, level.ToString(),
                DateTime.Now.ToString("yyyyMMdd") + ".log");
        }

        /// <summary>
        ///     格式化行
        /// </summary>
        /// <param name="info"></param>
        /// <returns></returns>
        private string FormatInfo(STDLog info)
        {
            List<KeyValuePair<string, object>> list = typeof(STDLog).GetProperties()
                .Select(item =>
                {
                    var descs = item.GetCustomAttributes(new DescriptionAttribute().GetType(), true);
                    DescriptionAttribute desc = null;
                    if (descs != null && descs.Count() > 0)
                    {
                        desc = descs[0] as DescriptionAttribute;
                        object value = item.GetValue(info, new List<object>().ToArray());
                        return new KeyValuePair<string, object>(desc.Description, value ?? string.Empty);
                    }
                    else
                    {
                        return new KeyValuePair<string, object>();
                    }
                })
                //.Union(info.Additional.Select(item => new KeyValuePair<string, object>(item.Key, item.Value)))
                .Where(item => !string.IsNullOrEmpty(item.Key))
                .ToList();
            return string.Join(FieldSplitString,
                list.Select(item => string.Format("{0}{1}{2}", item.Key, ValueSplitString, item.Value)));
        }

        /// <summary>
        ///     解析行
        /// </summary>
        /// <param name="line"></param>
        /// <returns></returns>
        private STDLog DeFormatInfo(string line)
        {
            var info = new STDLog();
            List<KeyValuePair<string, PropertyInfo>> properties = typeof(STDLog).GetProperties().Select(item =>
            {
                var desc = item.GetCustomAttributes(true)[0] as DescriptionAttribute;
                if (desc == null)
                    return new KeyValuePair<string, PropertyInfo>();
                return new KeyValuePair<string, PropertyInfo>(desc.Description, item);
            }).Where(item => !string.IsNullOrEmpty(item.Key)).ToList();
            line.Split(FieldSplitString).ForEach(item =>
            {
                try
                {
                    string[] pair = item.Trim().Split(ValueSplitString);
                    string desc = pair[0].Trim();
                    string value = pair[1].Trim();
                    KeyValuePair<string, PropertyInfo> property = properties.FirstOrDefault(data => data.Key == desc);
                    if (string.IsNullOrEmpty(property.Key))
                    {
                        //info.Additional.Add(desc, value);
                        return;
                    }
                    if (property.Value.PropertyType == typeof(DateTime) ||
                        property.Value.PropertyType == typeof(DateTime?))
                    {
                        DateTime time;
                        DateTime.TryParse(value, out time);
                        if (time == default(DateTime))
                            time = SqlDateTime.MinValue.Value;
                        property.Value.SetValue(info, time, null);
                        return;
                    }
                    if (property.Value.PropertyType == typeof(int))
                    {
                        property.Value.SetValue(info, string.IsNullOrEmpty(value)
                            ? 0
                            : int.Parse(value), null);
                    }

                    property.Value.SetValue(info, Convert.ChangeType(value, property.Value.PropertyType), null);
                }
                catch
                {
                }
            });
            return info;
        }

        #endregion
    }
}